/**  
 * @author renbangjie renbangjie@126.com  
 * @date 2015-9-23 下午2:21:19
 * @version V1.0  
 * <b>Copyright (c)</b> 2014医信金融信息服务（上海）有限公司-版权所有<br/>
 */
package com.yjd.comm.util;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import com.yjd.comm.cache.CachedUtil;

/**
 * 处理短信验证码攻击
 * @author renbangjie renbangjie@126.com
 * @date 2015-9-23 下午2:21:19
 */
public class MsgSecurityUtils extends SecurityCode {

	/**
	 * 缓存key
	 */
	private static final String CAPTCHA_CACHED_KEY = "captcha_cached_key";

	/**
	 * 发送次数
	 */
	private static final double CAPTCHA_GENARATOE_COUNT = 30;
	
	/**
	 * 频率
	 */
	private static final double CAPTCHA_RANGE = CAPTCHA_GENARATOE_COUNT / 10;
	
	
	/**
	 * 解除限制时间，单位秒
	 */
	private static final int CAPTCHA_TIME_OUT_SECONDS = 3600;
	
	
	/**
	 * 更新发送记录
	 * @param keys
	 * @throws Exception
	 *             void
	 */
	public static void updateCached(String... keys) throws Exception {
		for (String key : keys) {
			updateCached(key);
		}
	}

	/**
	 * 更新发送记录
	 * @param key
	 * @return boolean
	 * @throws Exception
	 */
	public static void updateCached(String key) throws Exception {

		Calendar cal = Calendar.getInstance();
		//获取缓存块
		Map<String, Queue<Long>> captchaMap = getCaptchaMap(CAPTCHA_CACHED_KEY);

		// 时间集合
		Queue<Long> statistics = captchaMap.get(key);
		if (statistics == null || statistics.isEmpty()) {
			captchaMap.put(key, initQueue(cal.getTimeInMillis()));
			CachedUtil.defaultCached().set(CAPTCHA_CACHED_KEY, captchaMap);
			return;
		}

		// 最大频率为10/10,被除数为次数,除数为时间(分钟)
		if (rangeVarible(statistics, CAPTCHA_RANGE)) {
			return;
		}

		// 超过10次则移除最早的记录
		if (statistics.size() >= CAPTCHA_GENARATOE_COUNT) {
			statistics.poll();
		}

		statistics.add(cal.getTimeInMillis());

		captchaMap.put(key, statistics);
		CachedUtil.defaultCached().set(CAPTCHA_CACHED_KEY, captchaMap);
	}
	
	
	/**
	 * 获取缓存块
	* @return Map<String,Queue<Long>>
	* @throws Exception
	 */
	private static Map<String, Queue<Long>> getCaptchaMap(String key) throws Exception {
		Map<String, Queue<Long>> captchaMap = (Map<String, Queue<Long>>) CachedUtil
				.defaultCached().get(key);
		if (captchaMap == null) {
			captchaMap = new HashMap<String, Queue<Long>>();
		}
		return captchaMap;
	}
	
	
	/**
	 * 是否达到最大频率
	 * @param key
	 * @return boolean 达到最大频率返回true
	 * @throws Exception
	 */
	public static boolean validate(String key) throws Exception {
		//获取缓存块
		Map<String, Queue<Long>> captchaMap = getCaptchaMap(CAPTCHA_CACHED_KEY);
		//获取key对应的缓存
		Queue<Long> statistics = captchaMap.get(key);
		if (statistics == null || statistics.isEmpty()) {
			return false;
		}
		// 最大频率为CAPTCHA_RANGE
		return rangeVarible(statistics, CAPTCHA_RANGE);

	}

	/**
	 * 初始化队列
	 * 
	 * @param args
	 * @return Queue<Long>
	 * @throws Exception
	 */
	private static Queue<Long> initQueue(Long... args) throws Exception {
		Queue<Long> linkedList = new LinkedList<Long>();
		for (Long temp : args) {
			linkedList.offer(temp);
		}
		return linkedList;
	}
	
	/**
	 * 一段时间内的频率
	 * 
	 * @param variable
	 * @return boolean
	 * @throws Exception
	 */
	private static boolean rangeVarible(Queue<Long> statistics, double rate)
			throws Exception {

		if (statistics.isEmpty() || statistics.size() <= 1) {
			return false;
		}

		// 开始计算日期
		long start = statistics.peek();
		
		List<Long> tmpList = new ArrayList<Long>();
		Iterator<Long> iterator = statistics.iterator();
		while (iterator.hasNext()) {
			tmpList.add(iterator.next());
		}

		// 最后一次发送日期
		long last = tmpList.get(tmpList.size() - 1);
		
		//时间间隔
		long sub = (System.currentTimeMillis() - start) / 1000;
		if(sub > CAPTCHA_TIME_OUT_SECONDS){
			CachedUtil.defaultCached().remove(CAPTCHA_CACHED_KEY);
			return false;
		}
		
		// 时间差,默认保留10位小数
		double by = ArithUtil.divs(last - start, 1000, 60);
		if (by <= 0) {
			return true;
		}

		// 每分钟的频率,默认保留10位小数
		double tmp = ArithUtil.divs(tmpList.size(), by);

		if (tmp >= rate) {
			return true;
		}

		return false;
	}

}
